package com.itesm.sistemasinteligentes.sat;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class SASAT extends SATSolver {

	private double maxTemp;
	private double minTemp;
	private final static boolean CHUCK_NORRIS = true;
	private final double decay;
	private double tempWhenSolution;
	
	public SASAT(int[][] clauses, int maxFlips, double minTemp, double maxTemp, int maxTries){
		super(clauses,maxFlips, maxTries);
		this.maxTemp = maxTemp;
		this.minTemp = minTemp;
		this.decay = (maxTemp-minTemp)/10000;
	}
	
	@Override
	public boolean solveImplementation() {		
		triesRequired = 0;
		double temp = maxTemp;
		while(triesRequired<maxTries){			
			int j = 0;
			flipsInLastTry = 0;
			setRandomSolutionValues();
			while(CHUCK_NORRIS){
				if (isValidSolution()) {	
					tempWhenSolution = temp;
					return true;
				}
				temp = (int)(maxTemp * Math.exp(-j*(decay)));
				if(temp < minTemp){
					break;
				}
				changeAnnealingValue(temp);
				j++; triesRequired++;
			}
		}
		return false;
	}

	private void changeAnnealingValue(double temp) {
		int clausesValidBeforeChange = clausesValid();		
		int clausesValidAfterChange = 0;				
		for (Map.Entry<Integer, Boolean> entry : variables.entrySet()) {			
			// Swap the actual variable
			entry.setValue(!entry.getValue());
			for (int i = 0; i < clauses.length; i++) {
				for (int j = 0; j < clauses[i].length; j++) {
					if ((clauses[i][j] > 0 && variables.get(Math
							.abs(clauses[i][j])))
							|| (clauses[i][j] < 0 && !variables.get(Math
									.abs(clauses[i][j])))) {
						clausesValidAfterChange++;
						break;
					}
				}
			}			
			// Swap the actual variable
			entry.setValue(!entry.getValue());
			double delta = clausesValidAfterChange - clausesValidBeforeChange;
			if(delta > 0){
				double desiredProbability =  (1/(1+Math.exp(-delta/temp)))*100;
				int random = (int)(Math.random()*100);
				if (random < desiredProbability) {					
					// Swap the actual variable
					entry.setValue(!entry.getValue());
					flipsInLastTry++;
					flipsRequired++;
				}				
			}
			clausesValidAfterChange = 0;
		}				
	}	
	
	@Override
	public String report() {	
		Workbook wb = new XSSFWorkbook();
		
		CreationHelper createHelper = wb.getCreationHelper();
	    Sheet sheet = wb.createSheet("SASAT results");

	    // Create a row and put some cells in it. Rows are 0 based.
	    Row row = sheet.createRow((short)0);
	    // Create a cell and put a value in it.
	    Cell cell = row.createCell(0);
	    cell.setCellValue(createHelper.createRichTextString("Tries"));
	    // Or do it on one line.
	    row.createCell(1).setCellValue(triesRequired);
	    
	    row = sheet.createRow((short)1);
	    // Create a cell and put a value in it.
	    cell = row.createCell(0);
	    cell.setCellValue(createHelper.createRichTextString("Flips"));
	    // Or do it on one line.
	    row.createCell(1).setCellValue(flipsRequired);
	    
	    row = sheet.createRow((short)2);
	    // Create a cell and put a value in it.
	    cell = row.createCell(0);
	    cell.setCellValue(createHelper.createRichTextString("Flips last try"));
	    // Or do it on one line.
	    row.createCell(1).setCellValue(flipsRequired);
	    
	    row = sheet.createRow((short)3);
	    // Create a cell and put a value in it.
	    cell = row.createCell(0);
	    cell.setCellValue(createHelper.createRichTextString("Min. Temp"));
	    // Or do it on one line.
	    row.createCell(1).setCellValue(minTemp);
		
	    row = sheet.createRow((short)4);
	    // Create a cell and put a value in it.
	    cell = row.createCell(0);
	    cell.setCellValue(createHelper.createRichTextString("Max. Temp."));
	    // Or do it on one line.
	    row.createCell(1).setCellValue(maxTemp);
	    
	    row = sheet.createRow((short)5);
	    // Create a cell and put a value in it.
	    cell = row.createCell(0);
	    cell.setCellValue(createHelper.createRichTextString("Temp. for Solution"));
	    // Or do it on one line.
	    row.createCell(1).setCellValue(tempWhenSolution);
	    
	    row = sheet.createRow((short)6);
	    // Create a cell and put a value in it.
	    cell = row.createCell(0);
	    cell.setCellValue(createHelper.createRichTextString("Time"));
	    // Or do it on one line.
	    row.createCell(1).setCellValue((endTime - startTime));
	    
		try {
			FileOutputStream fileOut;
			fileOut = new FileOutputStream("sasat.xlsx");
			wb.write(fileOut);
		    fileOut.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    
	    
		StringBuffer buffer = new StringBuffer();
		buffer.append("La solución encontrada utilizando SASAT es:\n");
		buffer.append("********************************************\n");
		buffer.append("Variable\t\tValor de Verdad\n");
		buffer.append("********************************************\n");
		for (Map.Entry<Integer, Boolean> entry : variables.entrySet()) {							
			buffer.append(entry.getKey()+"\t\t\t"+entry.getValue()+"\n");						
		}
		buffer.append("********************************************\n\n\n");
		buffer.append("Se requirieron "+ triesRequired + " intentos para obtener la solución.\n");
		buffer.append("Se requirieron "+ flipsRequired + " flips para obtener la solución.\n");
		buffer.append("Se requirieron unicamente "+ flipsInLastTry + " flips en el intento exitoso.\n");
		buffer.append("La temperatura mínima fue "+ minTemp + "\n");
		buffer.append("La temperatura máxima "+ maxTemp + " \n");
		buffer.append("La temperatura al momento de obtener la solución fue "+ tempWhenSolution + " \n");
		buffer.append("La ejecución duró "+ (endTime - startTime) + " milisegundos.\n");		
		return buffer.toString();
	}

}
